/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Base64 encoder/decoder implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * $Id: base64.cpp,v 1.6 2009-05-13 07:18:25 amaula Exp $
 */
#include "base64.hpp"
#include "owndebug.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
using namespace gim;
//*****************************************************************************
//*****************************************************************************
static unsigned char dtable64[64];
static unsigned char etable64[255]; 
static bool InitializeTables(void);
static bool tablesInitialized = InitializeTables();
//*****************************************************************************
//*****************************************************************************

static bool InitializeTables(void) 
{
  tablesInitialized = true;
  memcpy(dtable64, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 64);
  memset(etable64, 0x00, 255);
  for (int i=0; i<64; ++i) {
    etable64[ dtable64[i] ] = i;
  }
  return true;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CBase64::CBase64()
  : iDecodedBuffer(NULL),
    iDecodedBufferSize(0),
    iDecodedBufferReadOnly(false),
    iEncodedBuffer(NULL),
    iEncodedBufferSize(0),
    iEncodedBufferReadOnly(false)
{
}
//*****************************************************************************

CBase64::~CBase64()
{
  // Got own Decoded buffer? So clear it.
  if (iDecodedBuffer && !iDecodedBufferReadOnly) {
    delete[] iDecodedBuffer;
    iDecodedBuffer = NULL;
    iDecodedBufferSize = 0;
  }
  
  // Got own Encoded buffer? So clear it.
  if (iEncodedBuffer && !iEncodedBufferReadOnly) {
    delete[] iEncodedBuffer;
    iEncodedBuffer = NULL;
    iEncodedBufferSize = 0;
  }
}
//*****************************************************************************

CBase64::CBase64(const CBase64 &aSrc)
  : iDecodedBuffer(NULL),
    iDecodedBufferSize(0),
    iDecodedBufferReadOnly(false),
    iEncodedBuffer(NULL),
    iEncodedBufferSize(0),
    iEncodedBufferReadOnly(false)
{
  // Use the assignment operator.
  *this = aSrc;
}
//*****************************************************************************

CBase64 &CBase64::operator=(const CBase64 &aSrc)
{
  iDecodedBufferReadOnly = false;
  iEncodedBufferReadOnly = false;
  iDecodedBuffer = new unsigned char [aSrc.iDecodedBufferSize];
  iEncodedBuffer = new char [aSrc.iEncodedBufferSize];
  iDecodedBufferSize = aSrc.iDecodedBufferSize;
  iEncodedBufferSize = aSrc.iEncodedBufferSize;

  memcpy(iEncodedBuffer, aSrc.iEncodedBuffer, iEncodedBufferSize);
  memcpy(iDecodedBuffer, aSrc.iDecodedBuffer, iDecodedBufferSize);

  return *this;
}
//*****************************************************************************

bool CBase64::Encode(const unsigned char *aData, const unsigned int aDataSize, bool aCopyData)
{
  const unsigned int encoded_size = aDataSize + (aDataSize / 3) + 1;//(aDataSize % 3); // Required encoded space
  const unsigned int decoded_size = aDataSize;

  // Take copy of source data if requested, otherwise assign pointer and mark readonly
  if (aCopyData) {
    // Check whether we can/must free the existing buffer.
    if (!iDecodedBufferReadOnly && 
        iDecodedBuffer != NULL) delete[] iDecodedBuffer;
    
    // Reserve buffer.
    iDecodedBufferSize = decoded_size;
    iDecodedBuffer = new unsigned char [decoded_size];
    iDecodedBufferReadOnly = false;

    // Copy data.
    memcpy(iDecodedBuffer, aData, decoded_size);

  } else {
    // Check whether we can/must free the existing buffer.
    if (!iDecodedBufferReadOnly && 
        iDecodedBuffer != NULL) delete[] iDecodedBuffer;
    
    // Reserve & Assign.
    iDecodedBufferSize = decoded_size;
    iDecodedBuffer = const_cast<unsigned char *>(aData); // Can Remove constness, as we marked it readonly.
    iDecodedBufferReadOnly = true;
  }

  // Reserve & Init encoded buffer.
  if (!iEncodedBufferReadOnly &&
      iEncodedBuffer != NULL) delete[] iEncodedBuffer;
  iEncodedBuffer = new char [encoded_size + 1];
  iEncodedBufferSize = encoded_size;
  iEncodedBufferReadOnly = false;
  memset(iEncodedBuffer, 0x00, iEncodedBufferSize);


  // Do the encoding (Code ripped, so using it as-is)
  const unsigned char *B64 = dtable64;
  register unsigned int i,j,k; 
  register unsigned char m,t; 
  register unsigned int l = decoded_size;
  const unsigned char *h = iDecodedBuffer;
  char *d = iEncodedBuffer;
  
  if (!l) return false; 
  l <<= 3; // no. bits 
  m=0x80; 

  for (i=0,j=0,k=0,t=0; i<l; i++){ 
    if (h[(i>>3)]&m) t|=1; 
    j++; 
    if (!(m>>=1)) m=0x80; 
    if (!(j%6)) { 
      d[k]=B64[t]; 
      t&=0; 
      k++; 
    } 
    t<<=1; 
  } 
  m=5-(j%6); 
  t<<=m; 
  if (m) { 
    d[k]=B64[t]; 
    k++; 
  } 
  d[k] = 0; 
  
  const unsigned int final_length = strlen(d);
  dPrint(15,"final_length = %u, encoded_size = %u", final_length, encoded_size);
  assert(final_length == encoded_size && encoded_size == iEncodedBufferSize);

  return true; 
} 
//*****************************************************************************

bool CBase64::Decode(const char *aData, const unsigned int aDataSize, bool aCopyData)
{
  const unsigned int encoded_size = aDataSize; // Required encoded space
  const unsigned int decoded_size = (3*aDataSize)/4;
  
  // Take copy of source data if requested, otherwise assign pointer and mark readonly
  if (aCopyData) {
    // Check whether we can/must free the existing buffer.
    if (!iEncodedBufferReadOnly && 
        iEncodedBuffer != NULL) delete[] iEncodedBuffer;
    
    // Reserve buffer.
    iEncodedBufferSize = encoded_size;
    iEncodedBuffer = new char [encoded_size];
    iEncodedBufferReadOnly = false;

    // Copy data.
    memcpy(iEncodedBuffer, aData, encoded_size);
    
  } else {
    // Check whether we can/must free the existing buffer.
    if (!iEncodedBufferReadOnly && 
        iEncodedBuffer != NULL) delete[] iEncodedBuffer;
    
    // Reserve & Assign.
    iEncodedBufferSize = encoded_size;
    iEncodedBuffer = const_cast<char *>(aData); // Can Remove constness, as we marked it readonly.
    iEncodedBufferReadOnly = true;
  }

  // Reserve & Init encoded buffer.
  if (!iDecodedBufferReadOnly &&
      iDecodedBuffer != NULL) delete[] iDecodedBuffer;
  iDecodedBuffer = new unsigned char [decoded_size + 1];
  iDecodedBufferSize = decoded_size;
  iDecodedBufferReadOnly = false;
  memset(iDecodedBuffer, 0x00, iDecodedBufferSize);


  // Do the real decoding
  unsigned int i=0,k=0,l=encoded_size; 
  const unsigned char *b64 = etable64;
  const char *b = iEncodedBuffer;
  unsigned char *d = iDecodedBuffer;
  if (l < 2) return false;

  while (true) { 
    i++; 
    if (k+1<l) d[i-1] = (( b64 [ ( (int)b[k] ) ] ) << 2 ); 
    else break; 
    k++; 
    if (k<l) d[i-1]|=((b64[((int)b[k])])>>4); 
    else break; 
    i++; 
    if (k+1<l) d[i-1]=((b64[((int)b[k])])<<4); 
    else break; 
    k++; 
    if (k<l) d[i-1]|=((b64[((int)b[k])])>>2); 
    else break; 
    i++; 
    if (k+1<l) d[i-1]=((b64[((int)b[k])])<<6); 
    else break; 
    k++; 
    if (k<l) d[i-1]|=(b64[((int)b[k])]); 
    else break; 
    k++; 
  } 

  const unsigned int final_length = i-1;
  dPrint(15,"final_length = %u, decoded_size = %u", final_length, decoded_size);
  assert(final_length == decoded_size && decoded_size == iDecodedBufferSize);

  return true;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
